package org.cmroot.certmaster.service.impl;

import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.jce.provider.X509CertificateObject;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.util.StreamParsingException;
import org.bouncycastle.util.encoders.HexEncoder;
import org.bouncycastle.x509.NoSuchParserException;
import org.bouncycastle.x509.X509StreamParser;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.cmroot.certmaster.service.PKIEngine;
import org.cmroot.certmaster.service.PKIEngineException;

import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import sun.security.util.ObjectIdentifier;

/**
 * Copyright (c) 2010 Andrey Domas <andrey.domas at gmail.com>.
 * *
 * This file is part of certmaster.
 * *
 * Certmaster is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * *
 * Certmaster is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see http://www.gnu.org/licenses
 * or write to the Free Software Foundation,Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301  USA
 */

@Service
@Qualifier("bc_engine")
public class PKIEngineBCImpl implements PKIEngine {

    private static final BouncyCastleProvider bc_provider = new BouncyCastleProvider();

    public PKIEngineBCImpl() {
        Security.addProvider(bc_provider);
    }

    private static X509StreamParser getX509StreamParser() throws PKIEngineException {
        try {
            return X509StreamParser.getInstance("CERTIFICATE", "BC");
        } catch (NoSuchProviderException e) {
            throw new PKIEngineException(e);
        } catch (NoSuchParserException e) {
            throw new PKIEngineException(e);
        }
    }

    private static HashSet<X509Certificate> parse2CertsObj(X509StreamParser parser) throws PKIEngineException {
        Collection engine_certs_coll = null;

        try {
            engine_certs_coll = parser.readAll();
        } catch (StreamParsingException e) {
            throw new PKIEngineException(e);
        }

        if (engine_certs_coll.isEmpty()) {
            throw new PKIEngineException("Certificates not found!");
        }

        HashSet<X509Certificate> resultset = new HashSet<X509Certificate>();
        for (Object o : engine_certs_coll) {
            if (o instanceof X509CertificateObject) {
                resultset.add((X509Certificate) o);
            }
        }

        return resultset;
    }

    private static Object readPEM(String pem) throws PKIEngineException {
        PEMReader reader = new PEMReader(new StringReader(pem));
        try {
            return reader.readObject();
        } catch (IOException e) {
            throw new PKIEngineException(e);
        }
    }

    @Override
    public byte[] readRequest(String pem) throws PKIEngineException {
        Object obj = readPEM(pem);
        if (obj instanceof PKCS10CertificationRequest) {
            return ((PKCS10CertificationRequest) obj).getDEREncoded();
        } else {
            throw new PKIEngineException("PEM is correct, but it's not certificate request");
        }
    }

    @Override
    public List readRequest(byte[] der) {
        PKCS10CertificationRequest req = new PKCS10CertificationRequest(der);

        ArrayList result = new ArrayList();

        result.add(req.getCertificationRequestInfo().getSubject().getValues(X509Name.CN).toString());
        result.add(req.getCertificationRequestInfo().getAttributes().getDEREncoded());

        return result;
    }

    @Override
    public Set<X509Certificate> readCert(String pem) throws PKIEngineException {
        X509StreamParser engine_parser = getX509StreamParser();
        engine_parser.init(new ByteArrayInputStream(pem.getBytes()));
        return parse2CertsObj(engine_parser);
    }

    @Override
    public Set<X509Certificate> readCert(byte[] der) throws PKIEngineException {
        HashSet<X509CertificateObject> certs = null;

        X509StreamParser parser = getX509StreamParser();
        parser.init(der);
        return parse2CertsObj(parser);

    }

    @Override
    public PKIXCertPathBuilderResult verifyChain(X509Certificate rootCert,
                                                          List<X509Certificate> interCerts,
                                                          X509Certificate endCert)
            throws GeneralSecurityException {

        X509CertSelector cert_selector = new X509CertSelector();

        //prepare certificate selector
        cert_selector.setCertificate(endCert);

        //prepare certstore
        List store_list = new ArrayList();
        store_list.add(rootCert);
        if (interCerts != null) {
            store_list.addAll(interCerts);
        }
        store_list.add(endCert);
        CollectionCertStoreParameters params = new CollectionCertStoreParameters(store_list);

        //Build PKIX

        //add root CAs
        PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(
                Collections.singleton(new TrustAnchor(rootCert, null)),
                cert_selector);
        pkixParams.addCertStore(CertStore.getInstance("Collection", params, bc_provider));
        //disable CRL check
        pkixParams.setRevocationEnabled(false);

        //Build chain and verify it
        CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", bc_provider);
        return (PKIXCertPathBuilderResult) builder.build(pkixParams);

    }

    @Override
    public byte[] getExtension(byte[] der, ObjectIdentifier oid) throws PKIEngineException {

        Set<X509Certificate> certs = readCert(der);

        if (certs != null && !certs.isEmpty()) {
            X509Certificate cert = certs.iterator().next(); //work only with first entry
            return cert.getExtensionValue(oid.toString());
        } else {
            return null;
        }

    }

    private DERObject makeDER(byte[] data) throws IOException {
        ByteArrayInputStream inS = new ByteArrayInputStream(data);
        return (new ASN1InputStream(data)).readObject();
    }

    private String octetStringDecode(byte[] data) throws IOException {
        DERObject der_s = makeDER(data);
        if (der_s instanceof DERUTF8String) {
            return DERUTF8String.getInstance(der_s).getString();
        }
        return null;
    }

    public String derDecoder(byte data[]) throws PKIEngineException {

        try {
            DERObject der_obj = makeDER(data);
            if (der_obj instanceof DEROctetString) {
                return octetStringDecode(((DEROctetString) der_obj).getOctets());
            }
        } catch (IOException e) {
            throw new PKIEngineException(e);
        }

        return null;

    }

    @Override
    public X509Certificate signRequest(byte[] requestDer, X509Certificate ca) throws PKIEngineException {

        //TODO

        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();

        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }
}
